package com.boss.core.manage;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import com.boss.core.Boss;
import com.boss.core.BossDescriptor;
import com.boss.core.util.BossUtil;
import hudson.model.Action;
import com.thoughtworks.xstream.XStream;
import hudson.*;
import hudson.model.*;
import hudson.model.listeners.SaveableListener;
import hudson.util.XStream2;
import net.sf.json.JSONObject;

import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import hudson.model.Descriptor.FormException;
import hudson.model.ParameterDefinition.ParameterDescriptor;
import org.kohsuke.stapler.export.Exported;

/**
 * This class supports the custom management section for BOSS.
 * 'Manage BOSS'
 * @author Momin Noor Khan
 */

@Extension
public class BossConfiguration extends Boss implements Saveable {
    private volatile String description;

    @CopyOnWrite
    private volatile List<BossProperty> properties = new ArrayList<BossProperty>();

    @CopyOnWrite
    private volatile List<PropertySection> propertySections = new ArrayList<PropertySection>();

    @CopyOnWrite
    private volatile List<BossEnvironment> environments = new ArrayList<BossEnvironment>();

    private static final Map<String, BossConfiguration> byName = new TreeMap<String, BossConfiguration>(String.CASE_INSENSITIVE_ORDER);
    private static final XStream XSTREAM = new XStream2();
    private static final Logger LOGGER = Logger.getLogger(BossConfiguration.class.getName());

    public BossConfiguration() {
        load();
    }

    public BossConfiguration(String description) {
        this.description = description;
        load();
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl) super.getDescriptor();
    }

    public List<BossPropertyDescriptor> getPropertyDescriptors() {
        return Boss.getBossPropertyDescriptors();
    }

    private synchronized void load() {
        properties.clear();
        propertySections.clear();
        environments.clear();

        XmlFile config = getConfigFile();
        try {
            if (config.exists()) {
                config.unmarshal(this);
            }
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "Failed to load " + config, e);
        }

        // remove nulls that have failed to load
        BossUtil.removeNulls(properties);
        BossUtil.removeNulls(propertySections);
        BossUtil.removeNulls(environments);

        // allocate default instances if needed.
        // doing so after load makes sure that newly added user properties do get reflected
        for (BossPropertyDescriptor bossPropertyDescriptor : BossProperty.all()) {
            if (getProperty(bossPropertyDescriptor.clazz) == null) {
                BossProperty userProperty = bossPropertyDescriptor.newInstance(this);
                if (userProperty != null) {
                    properties.add(userProperty);
                }
            }
        }
    }

    public String getUrl() {
        return "boss-config";
    }

    public String getSearchUrl() {
        return "/boss-config";
    }

    /**
     * The URL of the boss-config page.
     */
    @Exported(visibility = 999)
    public String getAbsoluteUrl() {
        return Hudson.getInstance().getRootUrl() + getUrl();
    }

    @Exported
    public String getDescription() {
        return description;
    }

    public Map<Descriptor<BossProperty>, BossProperty> getProperties2() {
        return Descriptor.toMap(properties);
    }

    public List<BossProperty> getProperties() {
        return properties;
    }

    /**
     * Get property sections in the configuration specific to boss
     *
     * @return
     */
    public List<PropertySection> getPropertySections() {
        return propertySections;
    }

    public List<BossEnvironment> getEnvironments() {
        return environments;
    }

    /**
     * Gets the property section named as the specified value, if available
     *
     * @param propertySectionName
     * @return
     */
    public PropertySection getPropertySection(String propertySectionName) {
        if (propertySections != null) {
            for (PropertySection ps : propertySections) {
                if (ps.getName().equalsIgnoreCase(propertySectionName)) {
                    return ps;
                }
            }
        }
        return null;
    }

    public List<BossEnvironment> getEnvironments(boolean isDisabled) {
        List<BossEnvironment> resultList = new ArrayList<BossEnvironment>();
        for (BossEnvironment e : environments) {
            if (e.isDisabled() == isDisabled) {
                resultList.add(e);
            }
        }
        return resultList;
    }

    public BossEnvironment getEnvironment(String environmentName) {
        if (environments != null) {
            for (BossEnvironment e : environments) {
                if (e.getEnvironmentName().equalsIgnoreCase(environmentName)) {
                    return e;
                }
            }
        }
        return null;
    }

    public List<BossServer> getServers(String environmentName) {
        BossEnvironment environment = getEnvironment(environmentName);
        if (environment != null) {
            return environment.getServers();
        }
        return null;
    }

    public List<BossServer> getServers(String environmentName, boolean isDisabled) {
        List<BossServer> resultList = new ArrayList<BossServer>();
        BossEnvironment env = getEnvironment(environmentName);
        if (env != null) {
            for (BossServer s : env.getServers()) {
                if (s.isDisabled() == isDisabled) {
                    resultList.add(s);
                }
            }
        }
        return resultList;
    }

    public BossServer getServer(String environmentName, String serverName) {
        BossEnvironment environment = getEnvironment(environmentName);
        if (environment != null) {
            List<BossServer> servers = environment.getServers();
            for (BossServer s : servers) {
                if (s.getServerName().equalsIgnoreCase(serverName)) {
                    return s;
                }
            }
        }
        return null;
    }

    public List<BossPort> getPorts(String environmentName, String serverName) {
        List<BossPort> ports = new ArrayList<BossPort>();

        BossServer server = getServer(environmentName, serverName);
        if (server != null) {
            ports = server.getPorts();
        }
        return ports;
    }

    public List<BossPort> getPorts(String environmentName, String serverName, boolean isEnabled) {
        List<BossPort> resultList = new ArrayList<BossPort>();

        BossServer server = getServer(environmentName, serverName);
        if (server != null) {
            for (BossPort p : server.getPorts()) {
                if (p.isEnabled() == isEnabled) {
                    resultList.add(p);
                }
            }
        }
        return resultList;
    }

    /**
     * Gets the properties of the specified property section
     *
     * @param propertySectionName
     * @return
     */
    public List<BossProperty> getSectionProperties(String propertySectionName) {
        List<BossProperty> bpList = new ArrayList<BossProperty>();
        PropertySection ps = getPropertySection(propertySectionName);
        if(ps != null) {
            bpList = ps.getProperties();
        }
        return bpList;
    }

    /**
     * Gets the value of the specified property from the given property section
     *
     * @param propertySectionName
     * @param propertyName
     * @return
     */
    public String getPropertyValue(String propertySectionName, String propertyName) {
        List<BossProperty> bpList = getSectionProperties(propertySectionName);
        if (bpList != null) {
            for (BossProperty bp : bpList) {
                if (bp.getName().equalsIgnoreCase(propertyName)) {
                    return bp.getValue();
                }
            }
        }
        return null;
    }

    /**
     * Updates the boss admin object by adding a property.
     */
    public synchronized void addProperty(BossProperty property) throws IOException {
        BossProperty old = getProperty(property.getClass());
        List<BossProperty> propertyList = new ArrayList<BossProperty>(properties);
        if (old != null) {
            propertyList.remove(old);
        }
        propertyList.add(property);
        //p.setUser(this);
        properties = propertyList;
        save();
    }

    @Exported(name = "property", inline = true)
    public List<BossProperty> getAllProperties() {
        return Collections.unmodifiableList(properties);
    }

    /**
     * Gets the specific property, or null.
     */
    public <T extends BossProperty> T getProperty(Class<T> clazz) {
        for (BossProperty p : properties) {
            if (clazz.isInstance(p)) {
                return clazz.cast(p);
            }
        }
        return null;
    }

    /**
     * Accepts the new description.
     */
    public synchronized void doSubmitDescription(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        //checkPermission(Hudson.ADMINISTER);

        description = req.getParameter("description");
        save();

        rsp.sendRedirect(".");
    }

    /**
     * Reloads the configuration from disk.
     * TO-DO: This is not working. Find out how this can be integrated with Hudson's existing functionality
     */
    public static void reload() {
        // iterate over an array to be concurrency-safe
        for (BossConfiguration config : byName.values().toArray(new BossConfiguration[0])) {
            config.load();
        }
    }

    /**
     * Stop gap hack. Don't use it. To be removed in the trunk.
     */
    public static void clear() {
        byName.clear();
    }

    /**
     * Gets the file where we save our configuration.
     */
    protected final XmlFile getConfigFile() {
        return new XmlFile(XSTREAM, new File(getRootDir(), "config.xml"));
    }

    /**
     * Gets the directory where Hudson stores boss configuration.
     */
    private static File getRootDir() {
        return new File(Hudson.getInstance().getRootDir(), "boss-config");
    }

    /**
     * Save the settings to a file.
     */
    public synchronized void save() throws IOException {
        if (BulkChange.contains(this)) {
            return;
        }
        getConfigFile().write(this);
        SaveableListener.fireOnChange(this, getConfigFile());
    }

    public Api getApi() {
        return new Api(this);
    }

    /**
     * Submit the properties
     * @param req
     * @param rsp
     * @throws IOException
     * @throws ServletException
     * @throws FormException
     */
    public void doSubmitProperties(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        JSONObject formData = req.getSubmittedForm();
        List<BossProperty> properties = Descriptor.newInstancesFromHeteroList(req, formData, "properties", BossProperty.all());
        this.properties = properties;

        save();
        rsp.sendRedirect(".");
    }

    /**
     * Submit the property sections
     * @param req
     * @param rsp
     * @throws IOException
     * @throws ServletException
     * @throws FormException
     */
    public void doSubmitPropertySections(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        JSONObject formData = req.getSubmittedForm();
        List<PropertySection> propertySections = new ArrayList<PropertySection>();
        propertySections.addAll(req.bindJSONToList(PropertySection.class, formData.get("propertySections")));
        this.propertySections = propertySections;

        save();
        rsp.sendRedirect(".");
    }

    /**
     * Submit the environment sections
     * @param req
     * @param rsp
     * @throws IOException
     * @throws ServletException
     * @throws FormException
     */
    public void doSubmitEnvironments(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        JSONObject formData = req.getSubmittedForm();
        List<BossEnvironment> environments = new ArrayList<BossEnvironment>();
        environments.addAll(req.bindJSONToList(BossEnvironment.class, formData.get("environments")));
        environments = BossUtil.getUniqueEnvironments(environments);
        this.environments = environments;

        save();
        rsp.sendRedirect(".");
    }

    public Object getDynamic(String token) {
        for (BossProperty property : getProperties()) {
            if (property instanceof Action) {
                Action action = (Action) property;
                if (action.getUrlName().equals(token) || action.getUrlName().equals('/' + token)) {
                    return action;
                }
            }
        }
        return null;
    }

    @Extension
    public static final class DescriptorImpl extends BossDescriptor {

    }

    static {
        XSTREAM.alias("configuration", BossConfiguration.class);
        XSTREAM.alias("propertySection", PropertySection.class);
        XSTREAM.alias("bossStringProperty", BossStringProperty.class);
        XSTREAM.alias("environment", BossEnvironment.class);
        XSTREAM.alias("server", BossServer.class);
        XSTREAM.alias("port", BossPort.class);
    }

    public List<ParameterDescriptor> getJobPropertyDescriptors() {
        return Functions.getParameterDescriptors();
    }

    public List<BossPropertyDescriptor> getPropertyDescriptors3() {
        List<BossPropertyDescriptor> list = new ArrayList<BossPropertyDescriptor>();
        list.add((BossPropertyDescriptor) Hudson.getInstance().getDescriptor(BossEnvVarProperty.class));
        return list;
    }

    public Descriptor<? extends BossPropertyDescriptor> getEnvVarDescriptor() {
        return Hudson.getInstance().getDescriptor(BossEnvVarProperty.class);
    }

    public List<BossPropertyDescriptor> getPropertyDescriptors2() {
        return Boss.getBossPropertyDescriptors();
    }
}
